﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using Microsoft.Cci;

using PartialExpressionCompleter.Util;
using PartialExpressionCompleter.Experiment;

namespace PartialExpressionCompleter.Query
{
    class MethodCall : Expression
    {
        private readonly IMethodReference meth;
        private readonly IMethodDefinition methDef;
        private readonly List<Expression> args;
        private readonly bool user;
        private readonly ITypeDefinition type;
        private int? score;
        public MethodCall(IMethodReference meth, List<Expression> args, bool user, ITypeDefinition type)
        {
            this.meth = meth;
            this.methDef = meth.ResolvedMethod;
            this.args = args;
            this.user = user;
            this.type = type;

            this.TypeMember = methDef;
        }
        public MethodCall(IMethodReference meth, List<Expression> args, bool user)
            : this(meth, args, user, meth.Type.ResolvedType) { }

        public IMethodReference Method { get { return meth; } }
        public List<Expression> Arguments { get { return args.ToList(); } }

        public override bool IsComplete
        {
            get { return args.All(a => a.IsComplete); }
        }
        public override bool IsNonTrivialQuery
        {
            get { return args.Any(a => a.IsNonTrivialQuery); }
        }

        public bool IsStatic
        {
            get
            {
                return (meth.CallingConvention & CallingConvention.HasThis) == 0
                    && methDef.IsStatic;
            }
        }

        protected override IEnumerable<Expression> GetRewritings()
        {
            throw new NotImplementedException();
        }

        protected override IEnumerable<Expression> GetExactQueryResults()
        {
            if (IsComplete)
            {
                yield return this;
            }
            else
            {
                throw new NotImplementedException();
            }
        }

        public override ITypeDefinition Type
        {
            get { return type; }
        }

        public override string ToString()
        {
            return meth.ContainingType + "." + meth.Name.ToString()
                + (meth.GenericParameterCount > 0
                    ? "<" + meth.ResolvedMethod.GenericParameters.CommaSeparated() + ">" : "")
                + "(" + args.CommaSeparated() + ")";
        }

        public override bool IsUserProvided
        {
            get { return user; }
        }

        public override int Score<E>(QueryContext<E> context)
        {
            if (!score.HasValue)
            {
                score = ComputeScore(context);
            }
            return score.Value;
        }

        protected int ComputeScore<E>(QueryContext<E> context) where E:IExpression
        {
            return this.ComputeScore(context, this.Arguments);
        }

        public int ComputeScore<E>(QueryContext<E> context, IEnumerable<Expression> argIn) where E:IExpression
        {
            var args = argIn.ToList();

            int baseCost = this.BaseCost + args.Sum(a => a.BaseCost);

            var paramTypes = meth.ResolvedMethod.GetParameterTypes()
                .Select(t => t.ResolvedType).ToList();
            var argTypes = args.Select(a => a.Type);

            int typeDistance = 0;
            if (ScoreSwitches.IsEnabled(ScoreTypes.TypeDistance))
            {
                typeDistance = paramTypes.Zip(argTypes,
                                              (pt, at) => at == null
                                                  ? 0
                                                  : pt.DistanceFrom(at))
                                         .Sum();
                typeDistance *= ScoreSwitches.GetMultiplier(ScoreTypes.TypeDistance);
            }
            int absTypeDist = 0;
            if (ScoreSwitches.IsEnabled(ScoreTypes.AbstractTypeDistance))
            {
                var absTypeBonuses = paramTypes
                    .Select((type, pos) =>
                        context.TypeMeanings.GetParameterTypeMeaning(meth.ResolvedMethod, pos))
                    .Zip(args.Select(a => a.Meanings == null
                             ? new HashSet<TypeTerm>()
                             : context.TypeMeanings.GetAllValues(
                                a.Meanings.Where(m => m is IdentifiedTypeTerm)
                                          .Cast<IdentifiedTypeTerm>())),
                         (pterm, aterms) => aterms.Contains(pterm))
                    .ToList();
                var absTypeBonus = absTypeBonuses.Count(b => b);
                absTypeDist = argTypes.Count(t => t != null) - absTypeBonus;
                absTypeDist *= ScoreSwitches.GetMultiplier(ScoreTypes.AbstractTypeDistance);
            }

            int localCallCost = ScoreSwitches.IsEnabled(ScoreTypes.Static)
                && context.ContainingType != null
                && (!methDef.IsStatic
                    || !context.ContainingType.GetSuperTypes()
                        .Contains(methDef.ContainingTypeDefinition))
                ? ScoreSwitches.GetMultiplier(ScoreTypes.Static) : (sbyte)0;

            int nsScore = 0;
            if (ScoreSwitches.IsEnabled(ScoreTypes.Namespace))
            {
                var nsArgs = args.Where(a => a != null && a.Type != null)
                                 .Select(a => a.Type.GetNamespace())
                                 .Where(ns => !ns.IsPrimitive)
                                 .ToList();
                var thisNs = (args[0].Type ?? methDef.ContainingTypeDefinition).GetNamespace();
                if (!methDef.IsStatic
                    && nsArgs.SequenceEqual(thisNs.Singleton()))
                {
                    nsScore = 0;
                }
                else
                {
                    nsScore = -nsArgs.CommonPrefix().Length * ScoreSwitches.GetMultiplier(ScoreTypes.Namespace);
                }
            }

            return baseCost + typeDistance + absTypeDist + localCallCost + nsScore;
        }
    }
}
